/**
 * Copyright (c) 2016-present, Facebook, Inc.
 * All rights reserved.
 *
 * This source code is licensed under the BSD-style license found in the
 * LICENSE file in the root directory of this source tree. An additional grant
 * of patent rights can be found in the PATENTS file in the same directory.
 */

#include "Show.h"
#include "Tool.h"

#include <boost/format.hpp>
#include <stdio.h>
#include <sstream>
#include <string>
#include <vector>
#include <unordered_map>
#include "NativeOutliner_generated.h"

/*
 * This tool consumes the outline artifacts generated by the NativeOutliner
 * pass, and generates the native code that contains the outlined code;
 * the equivalent of 'throw new <Throwable>("string literal")'
 */

using namespace facebook::redex::outliner;

namespace {

constexpr static char kOutlineThrowsTemplate[] = R"template(

// Copyright 2004-present Facebook. All Rights Reserved.

#include <fb/fbjni.h>

using namespace facebook::jni;
using facebook::jni::Environment;

namespace facebook { namespace redex {

static constexpr const char* kClassName = "com/facebook/redex/NativeOutlined";

struct JThrowable : public jni::JavaClass<JThrowable, jni::JThrowable> {
  constexpr static auto kJavaDescriptor = "Ljava/lang/Throwable;";
};

static jni::local_ref<JThrowable> dispatchThrows(alias_ref<jobject>, jint idx) {
  const char* cls = nullptr;
  const char* msg = nullptr;
  switch (idx) {
%s
    default:
      cls = "java/lang/RuntimeException";
      msg = "Invalid outline throw index";
      break;
  }

  throwNewJavaException(cls, msg);

  /* unreachable */
  return jni::local_ref<JThrowable>(nullptr);
}

void registerOutlinerDispatch() {
  registerNatives(kClassName, {
    makeNativeMethod("$dispatch$throws", dispatchThrows)
  });
}

}} //facebook::redex

// Called when library is loaded by the first class which uses it.
JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void*) {
  return facebook::jni::initialize(vm, [] {
    facebook::redex::registerOutlinerDispatch();
  });
}

)template";

class NativeOutlinerCodegen : public Tool {
 public:
  NativeOutlinerCodegen() : Tool("native-outliner-codegen", "codegen helper for native outliner") {}

  virtual void add_options(po::options_description& options) const override {
  options.add_options()
    ("input,i",
     po::value<std::string>()->value_name("/tmp/artifacts.bin")->required(),
     "(input) path of an artifacts file emitted by NativeOutliner pass")
    ("output,o",
     po::value<std::string>()->value_name("NativeOutlined.cpp")->required(),
     "(output) path of generated code")
    ;
  }

  virtual void run(const po::variables_map& options) override {
    auto inpath = options["input"].as<std::string>();
    auto outpath = options["output"].as<std::string>();

    FILE* fdin = fopen(inpath.c_str(), "rb");
    fseek(fdin, 0L, SEEK_END);
    int length = ftell(fdin);
    fseek(fdin, 0L, SEEK_SET);
    char *data = new char[length];
    fread(data, sizeof(char), length, fdin);
    auto outlined_throws = GetOutlinedThrows(data);
    fclose(fdin);

    FILE* fdout = fopen(outpath.c_str(), "w");
    std::string cases = generateOutlineThrowsCases(outlined_throws);
    fprintf(fdout, kOutlineThrowsTemplate, cases.c_str());
    fclose(fdout);

    delete [] data;
  }

 private:
  std::string generateOutlineThrowsCases(const OutlinedThrows* outlined_throws) {
    std::string cases;
    int idx = 0;
    for (auto outlined_throw : *(outlined_throws->outlined_throws())) {
      auto kase = boost::format{ R"template(
    case %0d:
      cls = R"___(%1s)___";
      msg = R"___(%2s)___";
      break;
  )template" } % idx++ % outlined_throw->type()->c_str() % outlined_throw->msg()->c_str();
      cases += kase.str();
    }
    return cases;
  }
};

static NativeOutlinerCodegen s_tool;

}
